Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Identity] Support the 'closed' field in submit endpoint #2700

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,9 @@ extension StripeAPI {
}

}

extension StripeAPI.VerificationPage {
func copyWithNewMissings(newMissings: Set<StripeAPI.VerificationPageFieldType>) -> StripeAPI.VerificationPage {
return StripeAPI.VerificationPage(biometricConsent: self.biometricConsent, documentCapture: self.documentCapture, documentSelect: self.documentSelect, individual: self.individual, countryNotListed: self.countryNotListed, individualWelcome: self.individualWelcome, phoneOtp: self.phoneOtp, fallbackUrl: self.fallbackUrl, id: self.id, livemode: self.livemode, requirements: StripeAPI.VerificationPageRequirements(missing: newMissings), selfie: self.selfie, status: self.status, submitted: self.submitted, success: self.success, unsupportedClient: self.unsupportedClient)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ extension StripeAPI {
let status: Status
/// If true, the associated VerificationSession has been submitted for processing.
let submitted: Bool

/// If true, the associated VerificationSession has been closed and can no longer be modified.
/// After submitting, closed might be false if needs to fallback from phone verification to document verification.
let closed: Bool
}

}

extension StripeAPI.VerificationPageData {
/// When submitted but is not closed and there is still missing requirements, need to fallback.
func needsFallback() -> Bool {
return submitted && !closed && !requirements.missing.isEmpty
}

func submittedAndClosed() -> Bool {
return submitted && closed
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ extension StripeAPI {
let dob: Bool?
let name: Bool?
let address: Bool?
let phoneOtp: Bool?
}
}

Expand All @@ -36,7 +37,8 @@ extension StripeAPI.VerificationPageClearData {
idNumber: fields.contains(.idNumber),
dob: fields.contains(.dob),
name: fields.contains(.name),
address: fields.contains(.address)
address: fields.contains(.address),
phoneOtp: fields.contains(.phoneOtp)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -239,17 +239,41 @@ final class VerificationSheetController: VerificationSheetControllerProtocol {
else {
// Transition to generic error screen
transitionWithVerificaionPageDataResult(
nil,
updateDataResult,
completion: completion
)
return
}

// If finished collecting, submit and transition
if updateData.requirements.missing.isEmpty {
apiClient.submitIdentityVerificationPage().observe(on: .main) { [weak self] result in
self?.isVerificationPageSubmitted = (try? result.get())?.submitted == true
self?.transitionWithVerificaionPageDataResult(
result,
apiClient.submitIdentityVerificationPage().observe(on: .main) { [weak self] submittedData in
guard let self = self else { return }
self.isVerificationPageSubmitted = (try? submittedData.get())?.submittedAndClosed() == true

// Checking the response of submit
guard case .success(let resultData) = submittedData
else {
self.isVerificationPageSubmitted = false
self.transitionWithVerificaionPageDataResult(submittedData, completion: completion)
return
}

self.isVerificationPageSubmitted = resultData.submitted == true && resultData.closed == true

if resultData.needsFallback() {
// Checking the buffered VerificationPageResponse, update its missings with the new missings
guard let verificationPageResponse = try? self.verificationPageResponse?.get() else {
assertionFailure("Fail to get VerificationPageResponse is nil")
return
}
self.verificationPageResponse = .success(verificationPageResponse.copyWithNewMissings(newMissings: resultData.requirements.missing))
// clear collected data
self.collectedData = StripeAPI.VerificationPageCollectedData()

}
self.transitionWithVerificaionPageDataResult(
submittedData,
completion: completion
)
}
Expand Down Expand Up @@ -358,8 +382,8 @@ final class VerificationSheetController: VerificationSheetControllerProtocol {
func sendCannotVerifyPhoneOtpAndTransition(
completion: @escaping() -> Void
) {
apiClient.cannotPhoneVerifyOtp().observe(on: .main) { [weak self] result in
self?.transitionWithVerificaionPageDataResult(result, completion: completion)
apiClient.cannotPhoneVerifyOtp().observe(on: .main) { [weak self] updatedDataResult in
self?.transitionWithUpdatedDataResult(result: updatedDataResult)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,11 @@ extension VerificationSheetFlowController: VerificationSheetFlowControllerProtoc
// been submitted and they can't go back to edit their input.
let isSuccessState = nextViewController is SuccessViewController

// If it's biometric consent, it's either the first screen of a doc type verification, or the first doc-fallback screen of phone type verification, don't show go back.
let isBiometricConsent = nextViewController is BiometricConsentViewController

// Don't display a back button, so replace the navigation stack
if isTransitioningFromLoading || isTransitioningFromDebug || isSuccessState {
if isTransitioningFromLoading || isTransitioningFromDebug || isSuccessState || isBiometricConsent {
navigationController.setViewControllers([nextViewController], animated: shouldAnimate)
} else {
navigationController.pushViewController(nextViewController, animated: shouldAnimate)
Expand Down Expand Up @@ -328,8 +331,8 @@ extension VerificationSheetFlowController: VerificationSheetFlowControllerProtoc
let missingRequirements =
updateDataResponse?.requirements.missing ?? staticContent.requirements.missing

// Show success screen if submitted
if updateDataResponse?.submitted == true {
// Show success screen if submitted and closed
if updateDataResponse?.submittedAndClosed() == true {
return completion(
SuccessViewController(
successContent: staticContent.success,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ class PhoneOtpViewController: IdentityFlowViewController {
let bodyText = {
if let localPhoneNumber = sheetController.collectedData.phone {
// If phone number is collected locally use the non-nil number
return phoneOtpContent.body.replacingOccurrences(of: "&phone_number&", with: localPhoneNumber.phoneNumber?.suffix(4) ?? "")
return phoneOtpContent.body.replacingOccurrences(of: "{phone_number}", with: localPhoneNumber.phoneNumber?.suffix(4) ?? "")
} else {
// Otherwise use the server provided non-nil number
return phoneOtpContent.body.replacingOccurrences(of: "&phone_number&", with: phoneOtpContent.redactedPhoneNumber ?? "")
return phoneOtpContent.body.replacingOccurrences(of: "{phone_number}", with: phoneOtpContent.redactedPhoneNumber ?? "")
}
}()

Expand All @@ -99,9 +99,9 @@ class PhoneOtpViewController: IdentityFlowViewController {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

override func viewDidLoad() {
super.viewDidLoad()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateUI()
ccen-stripe marked this conversation as resolved.
Show resolved Hide resolved
generateOtp()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum VerificationPageDataMock: String, MockData {
case noErrors = "VerificationPageData_no_errors"
case noErrorsNeedback = "VerificationPageData_no_errors_needback"
case submitted = "VerificationPageData_submitted"
case submittedNotClosed = "VerificationPageData_submitted_not_closed"

static func noErrorsWithMissings(
with missingRequirements: Set<StripeAPI.VerificationPageFieldType>
Expand All @@ -53,7 +54,8 @@ enum VerificationPageDataMock: String, MockData {
missing: missingRequirements
),
status: noErrorsResponse.status,
submitted: noErrorsResponse.submitted
submitted: noErrorsResponse.submitted,
closed: noErrorsResponse.closed
)
}
}
Expand Down Expand Up @@ -168,7 +170,7 @@ enum VerificationPageDataUpdateMock {
enum PhoneOtpPageMock {
static let`default` = StripeAPI.VerificationPageStaticContentPhoneOtpPage(
title: "Enter verification code",
body: "Enter the code sent to you phone &phone_number& to continue.",
body: "Enter the code sent to you phone {phone_number} to continue.",
redactedPhoneNumber: "(***)*****35",
errorOtpMessage: "Error confirming verification code",
resendButtonText: "Resend code",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": "VS_123",
"status": "requires_input",
"submitted": false,
"closed": false,
"requirements": {
"errors": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": "VS_123",
"status": "requires_input",
"submitted": false,
"closed": false,
"requirements": {
"errors": [],
"missing": []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": "VS_123",
"status": "requires_input",
"submitted": false,
"closed": false,
"requirements": {
"errors": [],
"missing": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": "VS_123",
"status": "requires_input",
"submitted": true,
"closed": true,
"requirements": {
"errors": [],
"missing": []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "VS_123",
"status": "requires_input",
"submitted": true,
"closed": false,
"requirements": {
"errors": [],
"missing": [
"biometric_consent",
"id_document_front",
"id_document_back",
"id_document_type"
]
},

}
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ final class VerificationSheetControllerTest: XCTestCase {
idNumber: false,
dob: false,
name: false,
address: false
address: false,
phoneOtp: false
),
collectedData: mockData
)
Expand Down Expand Up @@ -525,6 +526,62 @@ final class VerificationSheetControllerTest: XCTestCase {
)
}

func testSaveDataSubmitsFallbackResponse() throws {
// Mock initial VerificationPage request successful
controller.verificationPageResponse = .success(try VerificationPageMock.response200.make())

// Mock time to submit
mockFlowController.isFinishedCollecting = true

let mockDataResponse = try VerificationPageDataMock.noErrors.make()
let mockSubmitResponse = try VerificationPageDataMock.submittedNotClosed.make()
let mockData = VerificationPageDataUpdateMock.default.collectedData!

// Mock number of attempted scans
controller.analyticsClient.countDidStartDocumentScan(for: .front)
controller.analyticsClient.countDidStartDocumentScan(for: .back)
controller.analyticsClient.countDidStartDocumentScan(for: .back)

// Save data
controller.saveAndTransition(from: .biometricConsent, collectedData: mockData) {
self.exp.fulfill()
}

// Respond to save data request with success
mockAPIClient.verificationPageData.respondToRequests(with: .success(mockDataResponse))

let submitRequestExp = expectation(description: "submit request made")
mockAPIClient.verificationSessionSubmit.callBackOnRequest {
submitRequestExp.fulfill()
}
wait(for: [submitRequestExp], timeout: 1)

// Verify submit request
XCTAssertEqual(mockAPIClient.verificationSessionSubmit.requestHistory.count, 1)
mockAPIClient.verificationSessionSubmit.respondToRequests(
with: .success(mockSubmitResponse)
)

// Verify completion block is called
wait(for: [exp], timeout: 1)

// Verify missing got updated
XCTAssertEqual(try controller.verificationPageResponse?.get().requirements.missing, mockSubmitResponse.requirements.missing)

// Verify collectedData got cleared
XCTAssertEqual(controller.collectedData, StripeAPI.VerificationPageCollectedData())

// Verify submitted is false
XCTAssertEqual(controller.isVerificationPageSubmitted, false)

// Verify response sent to flowController
wait(for: [mockFlowController.didTransitionToNextScreenExp], timeout: 1)
XCTAssertEqual(
try? mockFlowController.transitionedWithUpdateDataResult?.get(),
mockSubmitResponse
)
}

func testSaveDataSubmitsErrorResponse() throws {
let mockError = NSError(domain: "", code: 0, userInfo: nil)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ final class PhoneOtpViewControllerTest: XCTestCase {

vc = PhoneOtpViewController(phoneOtpContent: phoneOtpContent, sheetController: mockSheetController)

vc.viewDidAppear(false)
}

func testGenerateCodeOnceWhenLoads() {
Expand Down