Skip to content

Commit

Permalink
[Identity] Support test mode M1
Browse files Browse the repository at this point in the history
  • Loading branch information
ccen-stripe committed May 5, 2023
1 parent ee11156 commit 9ea2b7d
Show file tree
Hide file tree
Showing 11 changed files with 598 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ protocol IdentityAPIClient: AnyObject {
purpose: String,
fileName: String
) -> Future<STPAPIClient.FileAndUploadMetrics>

func verifyTestVerificationSession(
simulateDelay: Bool
) -> Promise<StripeAPI.VerificationPageData>

func unverifyTestVerificationSession(
simulateDelay: Bool
) -> Promise<StripeAPI.VerificationPageData>
}

final class IdentityAPIClientImpl: IdentityAPIClient {
Expand Down Expand Up @@ -110,6 +118,20 @@ final class IdentityAPIClientImpl: IdentityAPIClient {
ownedBy: verificationSessionId
)
}

func verifyTestVerificationSession(simulateDelay: Bool) -> Promise<StripeAPI.VerificationPageData> {
return apiClient.post(
resource: APIEndpointVerificationPageTestingVerify(id: verificationSessionId),
parameters: ["simulate_delay": simulateDelay]
)
}

func unverifyTestVerificationSession(simulateDelay: Bool) -> Promise<StripeAPI.VerificationPageData> {
return apiClient.post(
resource: APIEndpointVerificationPageTestingUnverify(id: verificationSessionId),
parameters: ["simulate_delay": simulateDelay]
)
}
}

private func APIEndpointVerificationPage(id: String) -> String {
Expand All @@ -121,3 +143,9 @@ private func APIEndpointVerificationPageData(id: String) -> String {
private func APIEndpointVerificationPageSubmit(id: String) -> String {
return "identity/verification_pages/\(id)/submit"
}
private func APIEndpointVerificationPageTestingVerify(id: String) -> String {
return "identity/verification_pages/\(id)/testing/verify"
}
private func APIEndpointVerificationPageTestingUnverify(id: String) -> String {
return "identity/verification_pages/\(id)/testing/unverify"
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,54 +148,4 @@ extension String.Localized {
)
}

// MARK: - Debug View
static var testModeTitle: String {
STPLocalizedString(
"You're currently in testmode",
"Title of test mode screen"
)
}

static var testModeContent: String {
STPLocalizedString(
"This page is only shown in testmode.",
"Explains the usages of test mode screen."
)
}

static var finishMobileFlow: String {
STPLocalizedString(
"Terminate mobile SDK flow",
"Title to terminate mobile SDK flow in test mode"
)
}

static var finishMobileFlowDetails: String {
STPLocalizedString(
"Terminate mobile SDK flow locally with Completed, Cancelled or Failed without changing the verification session on server.",
"Explains how terminate works. Don't translate Completed, Cancelled and Failed."
)
}

static var previewUserExperience: String {
STPLocalizedString(
"Preview user experience",
"Label text for previewing user experience"
)
}

static var previewUserExperienceDetails: String {
STPLocalizedString(
"Proceed to preview as an end user. Information provided will not be verified.",
"Explains the usages of preview user experience"
)
}

static var proceed: String {
STPLocalizedString(
"Proceed",
"Proceed button text"
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ protocol VerificationSheetControllerProtocol: AnyObject {
completion: @escaping () -> Void
)

func verifyAndTransition(
simulateDelay: Bool
)

func unverifyAndTransition(
simulateDelay: Bool
)

/// Transition to CountryNotListedViewController without any API request
func transitionToCountryNotListed(
missingType: IndividualFormElement.MissingType
Expand Down Expand Up @@ -304,6 +312,34 @@ final class VerificationSheetController: VerificationSheetControllerProtocol {
}
}

func verifyAndTransition(
simulateDelay: Bool
) {
apiClient.verifyTestVerificationSession(
simulateDelay: simulateDelay
).observe(on: .main) { [weak self] result in
self?.saveCheckSubmitAndTransition(
collectedData: nil,
updateDataResult: result,
completion: {}
)
}
}

func unverifyAndTransition(
simulateDelay: Bool
) {
apiClient.unverifyTestVerificationSession(
simulateDelay: simulateDelay
).observe(on: .main) { [weak self] result in
self?.saveCheckSubmitAndTransition(
collectedData: nil,
updateDataResult: result,
completion: {}
)
}
}

// MARK: - Transition without save
func transitionToCountryNotListed(missingType: IndividualFormElement.MissingType) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import UIKit
@available(iOSApplicationExtension, unavailable)
final class DebugViewController: IdentityFlowViewController {
private let debugView = DebugView()

init(
sheetController: VerificationSheetControllerProtocol
) {
super.init(
sheetController: sheetController, analyticsScreenName: .debug, shouldShowCancelButton: false
)
debugView.delegate = self
}

required init?(
Expand All @@ -38,10 +40,19 @@ final class DebugViewController: IdentityFlowViewController {
configure(backButtonTitle: nil, viewModel: .init(headerViewModel: nil, contentView: debugView, buttons: []))
}

private func didTapButton(_ type: DebugView.DebugButton) {
func didTapButton(_ type: DebugView.DebugButton) {
switch type {
case .completed:
finishWithResult(result: .flowCompleted)
case .submit(let completeOption):
switch completeOption {
case .success:
self.sheetController?.verifyAndTransition(simulateDelay: false)
case .failure:
self.sheetController?.unverifyAndTransition(simulateDelay: false)
case .successAsync:
self.sheetController?.verifyAndTransition(simulateDelay: true)
case .failureAsync:
self.sheetController?.unverifyAndTransition(simulateDelay: true)
}
case .cancelled:
finishWithResult(result: .flowCanceled)
case .failed:
Expand Down Expand Up @@ -69,3 +80,10 @@ extension StripeUICore.Button {
self.configuration = .identityPrimary()
}
}

@available(iOSApplicationExtension, unavailable)
extension DebugViewController: DebugViewDelegate {
func debugOptionsDidChange() {
self.updateUI()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//
// CompleteOptionView.swift
// StripeIdentity
//
// Created by Chen Cen on 5/4/23.
//

import Foundation
@_spi(STP) import StripeUICore
import UIKit

protocol CompleteOptionViewDelegate: AnyObject {
func didTapOption(completeOption: CompleteOptionView.CompleteOption)
}

class CompleteOptionView: UIControl {
enum CompleteOption {
case success
case failure
case successAsync
case failureAsync
}

weak var delegate: CompleteOptionViewDelegate?

private let label = UILabel()

private let radioButton = RadioButton()

private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.spacing = 20
return stackView
}()

override var isSelected: Bool {
didSet {
radioButton.isOn = isSelected
}
}

private let completeOption: CompleteOption

init(title: String, option: CompleteOption) {
self.completeOption = option
super.init(frame: .zero)
self.isSelected = false
label.text = title
stackView.addArrangedSubview(radioButton)
stackView.addArrangedSubview(label)

NSLayoutConstraint.activate([
radioButton.widthAnchor.constraint(equalToConstant: 20),
radioButton.leadingAnchor.constraint(equalTo: stackView.leadingAnchor, constant: 8),
])

addAndPinSubview(stackView)
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTap)))
}

@objc fileprivate func didTap() {
delegate?.didTapOption(completeOption: completeOption)
}

required init?(
coder: NSCoder
) {
fatalError("init(coder:) has not been implemented")
}
}

extension CompleteOptionView {
class RadioButton: UIView {
struct Constants {
static let diameter: CGFloat = 20
static let innerDiameter: CGFloat = 8
static let borderWidth: CGFloat = 1
}

public var isOn: Bool = true {
didSet {
update()
}
}

// change the color
public var borderColor: UIColor = IdentityUI.separatorColor {
didSet {
applyStyling()
}
}

/// Layer for the "off" state.
private let offLayer: CALayer = {
let layer = CALayer()
layer.bounds = CGRect(x: 0, y: 0, width: Constants.diameter, height: Constants.diameter)
layer.cornerRadius = Constants.diameter / 2
layer.borderWidth = Constants.borderWidth
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
return layer
}()

/// Layer for the "on" state.
private let onLayer: CALayer = {
let layer = CALayer()
layer.bounds = CGRect(x: 0, y: 0, width: Constants.diameter, height: Constants.diameter)
layer.cornerRadius = Constants.diameter / 2
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)

let innerCircle = CALayer()
innerCircle.backgroundColor = UIColor.white.cgColor
innerCircle.cornerRadius = Constants.innerDiameter / 2
innerCircle.bounds = CGRect(x: 0, y: 0, width: Constants.innerDiameter, height: Constants.innerDiameter)
innerCircle.anchorPoint = CGPoint(x: 0.5, y: 0.5)

// Add and center inner circle
layer.addSublayer(innerCircle)
innerCircle.position = CGPoint(x: layer.bounds.midX, y: layer.bounds.midY)

return layer
}()

override var intrinsicContentSize: CGSize {
return CGSize(width: Constants.diameter, height: Constants.diameter)
}

init() {
super.init(frame: .zero)
layer.addSublayer(offLayer)
layer.addSublayer(onLayer)
update()
applyStyling()
}

required init?(coder: NSCoder) {
super.init(coder: coder)
applyStyling()
}

override func layoutSubviews() {
offLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
onLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
}

override func tintColorDidChange() {
super.tintColorDidChange()
applyStyling()
}

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
applyStyling()
}

// MARK: - Private methods

private func update() {
CATransaction.begin()
CATransaction.setDisableActions(true)
offLayer.isHidden = isOn
onLayer.isHidden = !isOn
CATransaction.commit()
}

private func applyStyling() {
CATransaction.begin()
CATransaction.setDisableActions(true)
offLayer.borderColor = borderColor.cgColor
onLayer.backgroundColor = tintColor.cgColor

CATransaction.commit()
}

}

}
Loading

0 comments on commit 9ea2b7d

Please sign in to comment.