Add analytics to IdentityVerificationSheet (#155)
This change adds analytics to the IDV sheet. Specifically, we're logging the following events: - `stripeios.idprod.verification_sheet.presented` - the sheet is presented to the end-user - `stripeios.idprod.verification_sheet.closed` - the sheet is closed by the end-user. Also contains a `session_result` to indicate whether the user successfully completed the IDV flow or closed the sheet to cancel out of the flow. - `stripeios.idprod.verification_sheet.failed` - the sheet failed to open. Also contains an `error_dictionary` containing the error code, domain, and userInfo dictionary for the error that caused the sheet to fail to open.
This commit is contained in:
parent
16c2d2c28b
commit
336be4a0e3
|
@ -674,6 +674,8 @@
|
|||
E60437F225D34316006E2E03 /* STPGenericInputPickerFieldSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60437F125D34316006E2E03 /* STPGenericInputPickerFieldSnapshotTests.swift */; };
|
||||
E60437F625D37DDC006E2E03 /* STPGenericInputPickerFieldValidatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E60437F525D37DDC006E2E03 /* STPGenericInputPickerFieldValidatorTest.swift */; };
|
||||
E62F3A7425F80F9A00B2C0AC /* UIActivityIndicatorView+Stripe.swift in Sources */ = {isa = PBXBuildFile; fileRef = E62F3A7325F80F9A00B2C0AC /* UIActivityIndicatorView+Stripe.swift */; };
|
||||
E633417D25FC2376006F084B /* STPAnalyticEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E633417C25FC2376006F084B /* STPAnalyticEvent.swift */; };
|
||||
E633418425FC2FEA006F084B /* VerificationSheetAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = E633418325FC2FEA006F084B /* VerificationSheetAnalytics.swift */; };
|
||||
E66D20A925F02D9B00ED6CA0 /* IdentityVerificationSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D20A825F02D9B00ED6CA0 /* IdentityVerificationSheet.swift */; };
|
||||
E66D20AD25F0303500ED6CA0 /* VerificationFlowWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D20AC25F0303500ED6CA0 /* VerificationFlowWebViewController.swift */; };
|
||||
E66D20B025F031FB00ED6CA0 /* VerificationFlowWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D20AF25F031FB00ED6CA0 /* VerificationFlowWebView.swift */; };
|
||||
|
@ -695,10 +697,12 @@
|
|||
E6A598EB25F70729003B4987 /* VerificationFlowWebViewTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598E725F706A7003B4987 /* VerificationFlowWebViewTest.swift */; };
|
||||
E6A598F025F715B7003B4987 /* VerificationFlowWebViewControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598ED25F71599003B4987 /* VerificationFlowWebViewControllerTest.swift */; };
|
||||
E6A598F725F72272003B4987 /* VerificationClientSecretTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598F325F721D3003B4987 /* VerificationClientSecretTest.swift */; };
|
||||
E6A598FC25F72B96003B4987 /* IdentityVerificationSheetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598F925F72B7E003B4987 /* IdentityVerificationSheetTest.swift */; };
|
||||
E6B0F5592686C0BA00055EAF /* StripeCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E6B0F5582686C0BA00055EAF /* StripeCore.framework */; };
|
||||
E6B8971F25FC4FD6002428E1 /* MockAnalyticsClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8971E25FC4FD6002428E1 /* MockAnalyticsClient.swift */; };
|
||||
E6A598FC25F72B96003B4987 /* IdentityVerificationSheetTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6A598F925F72B7E003B4987 /* IdentityVerificationSheetTest.swift */; };
|
||||
E6B8972525FC532B002428E1 /* Analytic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8972125FC5311002428E1 /* Analytic.swift */; };
|
||||
E6CA4B3225F1C98300D8D9E8 /* mock.html in Resources */ = {isa = PBXBuildFile; fileRef = E6CA4B3125F1C98300D8D9E8 /* mock.html */; };
|
||||
E6E7A27C25FC65110002F914 /* VerificationSheetAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6E7A27B25FC65110002F914 /* VerificationSheetAnalyticsTest.swift */; };
|
||||
E6B8972525FC532B002428E1 /* Analytic.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6B8972125FC5311002428E1 /* Analytic.swift */; };
|
||||
F1122A7E1DFB84E000A8B1AF /* UINavigationBar+StripeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F1122A7D1DFB84E000A8B1AF /* UINavigationBar+StripeTest.m */; };
|
||||
F116E94C1D83405E0026A52A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11C74B9B164043050071C2CA /* Foundation.framework */; };
|
||||
|
@ -1460,6 +1464,8 @@
|
|||
E60437F125D34316006E2E03 /* STPGenericInputPickerFieldSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputPickerFieldSnapshotTests.swift; sourceTree = "<group>"; };
|
||||
E60437F525D37DDC006E2E03 /* STPGenericInputPickerFieldValidatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPGenericInputPickerFieldValidatorTest.swift; sourceTree = "<group>"; };
|
||||
E62F3A7325F80F9A00B2C0AC /* UIActivityIndicatorView+Stripe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivityIndicatorView+Stripe.swift"; sourceTree = "<group>"; };
|
||||
E633417C25FC2376006F084B /* STPAnalyticEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPAnalyticEvent.swift; sourceTree = "<group>"; };
|
||||
E633418325FC2FEA006F084B /* VerificationSheetAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetAnalytics.swift; sourceTree = "<group>"; };
|
||||
E66D20A825F02D9B00ED6CA0 /* IdentityVerificationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityVerificationSheet.swift; sourceTree = "<group>"; };
|
||||
E66D20AC25F0303500ED6CA0 /* VerificationFlowWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebViewController.swift; sourceTree = "<group>"; };
|
||||
E66D20AF25F031FB00ED6CA0 /* VerificationFlowWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationFlowWebView.swift; sourceTree = "<group>"; };
|
||||
|
@ -1487,6 +1493,7 @@
|
|||
E6B8971E25FC4FD6002428E1 /* MockAnalyticsClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAnalyticsClient.swift; sourceTree = "<group>"; };
|
||||
E6B8972125FC5311002428E1 /* Analytic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytic.swift; sourceTree = "<group>"; };
|
||||
E6CA4B3125F1C98300D8D9E8 /* mock.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = mock.html; path = MockFiles/mock.html; sourceTree = "<group>"; };
|
||||
E6E7A27B25FC65110002F914 /* VerificationSheetAnalyticsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerificationSheetAnalyticsTest.swift; sourceTree = "<group>"; };
|
||||
ED627D57207EA348007EFC56 /* nb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nb; path = Localizations/nb.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F1122A7D1DFB84E000A8B1AF /* UINavigationBar+StripeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UINavigationBar+StripeTest.m"; sourceTree = "<group>"; };
|
||||
F148ABE71D5E805A0014FD92 /* en */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = en; path = Localizations/en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
|
@ -2848,6 +2855,7 @@
|
|||
E66D20BC25F05C7C00ED6CA0 /* IdentityVerificationSheetError.swift */,
|
||||
E66D20B725F057FD00ED6CA0 /* VerificationClientSecret.swift */,
|
||||
E66D20C125F0643200ED6CA0 /* VerifyWebURLHelper.swift */,
|
||||
E633418325FC2FEA006F084B /* VerificationSheetAnalytics.swift */,
|
||||
);
|
||||
name = Identity;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2892,6 +2900,7 @@
|
|||
E6A598ED25F71599003B4987 /* VerificationFlowWebViewControllerTest.swift */,
|
||||
E6A598F325F721D3003B4987 /* VerificationClientSecretTest.swift */,
|
||||
E6A598F925F72B7E003B4987 /* IdentityVerificationSheetTest.swift */,
|
||||
E6E7A27B25FC65110002F914 /* VerificationSheetAnalyticsTest.swift */,
|
||||
);
|
||||
name = Identity;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3307,6 +3316,7 @@
|
|||
3111C60F252D270D00207E32 /* STPPaymentMethodSofortTests.swift in Sources */,
|
||||
8BD87B951EFB1CB100269C2B /* STPSourceVerificationTest.m in Sources */,
|
||||
36E283F8254A35210028C186 /* STPCardCVCInputTextFieldValidatorTests.swift in Sources */,
|
||||
E6E7A27C25FC65110002F914 /* VerificationSheetAnalyticsTest.swift in Sources */,
|
||||
36D7A91E253111E7009F2978 /* STPFloatingPlaceholderTextFieldSnapshotTests.swift in Sources */,
|
||||
B66FA0BE267EE204008D7F1D /* FormElementTest.swift in Sources */,
|
||||
B646C25526827F1200F4EAE5 /* TextFieldElement+IBANTest.swift in Sources */,
|
||||
|
@ -3586,6 +3596,7 @@
|
|||
B6E40EC62542541100A5BABD /* Events.swift in Sources */,
|
||||
B6D9CEDB251AA99100AAD424 /* STPPinManagementService.swift in Sources */,
|
||||
3111BE9F251316E600288D28 /* STPAddCardViewController.swift in Sources */,
|
||||
E633418425FC2FEA006F084B /* VerificationSheetAnalytics.swift in Sources */,
|
||||
3194907425140C9700AD8F0B /* STPPaymentOptionsViewController.swift in Sources */,
|
||||
B6E40EBC2542541100A5BABD /* PaymentSheet.swift in Sources */,
|
||||
366ECD38254B4AFA0082868E /* STPCardExpiryInputTextFieldFormatter.swift in Sources */,
|
||||
|
|
|
@ -35,8 +35,18 @@ final public class IdentityVerificationSheet {
|
|||
- Parameters:
|
||||
- verificationSessionClientSecret: The client secret of the Stripe VerificationSession object.
|
||||
*/
|
||||
public init(verificationSessionClientSecret: String) {
|
||||
public convenience init(verificationSessionClientSecret: String) {
|
||||
self.init(verificationSessionClientSecret: verificationSessionClientSecret,
|
||||
analyticsClient: STPAnalyticsClient.sharedClient)
|
||||
}
|
||||
|
||||
init(verificationSessionClientSecret: String,
|
||||
analyticsClient: STPAnalyticsClientProtocol) {
|
||||
self.verificationSessionClientSecret = verificationSessionClientSecret
|
||||
self.clientSecret = VerificationClientSecret(string: verificationSessionClientSecret)
|
||||
self.analyticsClient = analyticsClient
|
||||
|
||||
analyticsClient.addClass(toProductUsageIfNecessary: IdentityVerificationSheet.self)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,7 +78,10 @@ final public class IdentityVerificationSheet {
|
|||
) {
|
||||
// Overwrite completion closure to retain self until called
|
||||
let completion: (VerificationFlowResult) -> Void = { result in
|
||||
// TODO(mludowise|IDPROD-1438): Add analytics to log completion or error
|
||||
self.analyticsClient.log(analytic: VerificationSheetCompletionAnalytic.make(
|
||||
verificationSessionId: self.clientSecret?.verificationSessionId,
|
||||
sessionResult: result
|
||||
))
|
||||
completion(result)
|
||||
self.completion = nil
|
||||
}
|
||||
|
@ -85,7 +98,7 @@ final public class IdentityVerificationSheet {
|
|||
}
|
||||
|
||||
// Validate client secret
|
||||
guard let clientSecret = VerificationClientSecret(string: verificationSessionClientSecret) else {
|
||||
guard let clientSecret = clientSecret else {
|
||||
completion(.flowFailed(error: IdentityVerificationSheetError.invalidClientSecret))
|
||||
return
|
||||
}
|
||||
|
@ -94,13 +107,20 @@ final public class IdentityVerificationSheet {
|
|||
clientSecret: clientSecret,
|
||||
delegate: self
|
||||
)
|
||||
// TODO(mludowise|IDPROD-1438): Add analytics for starting flow
|
||||
analyticsClient.log(analytic: VerificationSheetPresentedAnalytic(verificationSessionId: clientSecret.verificationSessionId))
|
||||
presentingViewController.present(navigationController, animated: true)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Analytics client to use for logging analytics
|
||||
private let analyticsClient: STPAnalyticsClientProtocol
|
||||
|
||||
/// Completion block called when the sheet is closed or fails to open
|
||||
private var completion: ((VerificationFlowResult) -> Void)?
|
||||
|
||||
/// Parsed client secret string
|
||||
private let clientSecret: VerificationClientSecret?
|
||||
}
|
||||
|
||||
// MARK: - VerificationFlowWebViewControllerDelegate
|
||||
|
@ -112,3 +132,9 @@ extension IdentityVerificationSheet: VerificationFlowWebViewControllerDelegate {
|
|||
completion?(result)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - STPAnalyticsProtocol
|
||||
|
||||
extension IdentityVerificationSheet: STPAnalyticsProtocol {
|
||||
static var stp_analyticsIdentifier = "IdentityVerificationSheet"
|
||||
}
|
||||
|
|
|
@ -16,9 +16,44 @@ public enum IdentityVerificationSheetError: Error {
|
|||
case invalidClientSecret
|
||||
/// An unknown error.
|
||||
case unknown(debugDescription: String)
|
||||
}
|
||||
|
||||
extension IdentityVerificationSheetError: LocalizedError {
|
||||
/// Localized description of the error
|
||||
public var localizedDescription: String {
|
||||
return NSError.stp_unexpectedErrorMessage()
|
||||
}
|
||||
}
|
||||
|
||||
extension IdentityVerificationSheetError: CustomDebugStringConvertible {
|
||||
public var debugDescription: String {
|
||||
switch self {
|
||||
case .invalidClientSecret:
|
||||
return "Invalid client secret"
|
||||
case .unknown(debugDescription: let debugDescription):
|
||||
return debugDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(mludowise|MOBILESDK-193): Added `CustomNSError` conformance so our
|
||||
// analytics will be able to log useful information until we find a better solution.
|
||||
extension IdentityVerificationSheetError: CustomNSError {
|
||||
public static let errorDomain = "Stripe.\(IdentityVerificationSheetError.self)"
|
||||
|
||||
public var errorCode: Int {
|
||||
switch self {
|
||||
case .invalidClientSecret:
|
||||
return 0
|
||||
case .unknown:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
public var errorUserInfo: [String : Any] {
|
||||
return [
|
||||
NSDebugDescriptionErrorKey: debugDescription,
|
||||
NSLocalizedDescriptionKey: localizedDescription
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,8 +53,8 @@ final class VerificationFlowWebViewController: UIViewController {
|
|||
- clientSecret: The VerificationSession client secret.
|
||||
- delegate: Optional delegate for the `VerificationFlowWebViewController`
|
||||
*/
|
||||
private init(clientSecret: VerificationClientSecret,
|
||||
delegate: VerificationFlowWebViewControllerDelegate?) {
|
||||
init(clientSecret: VerificationClientSecret,
|
||||
delegate: VerificationFlowWebViewControllerDelegate?) {
|
||||
self.verificationWebView = VerificationFlowWebView(initialURL: VerifyWebURLHelper.startURL(fromToken: clientSecret.urlToken))
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
self.delegate = delegate
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// VerificationSheetAnalytics.swift
|
||||
// StripeiOS
|
||||
//
|
||||
// Created by Mel Ludowise on 3/12/21.
|
||||
// Copyright © 2021 Stripe, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Analytic that contains a `verification_session` payload param
|
||||
protocol VerificationSheetAnalytic: Analytic {
|
||||
var verificationSessionId: String? { get }
|
||||
var additionalParams: [String: Any] { get }
|
||||
}
|
||||
|
||||
extension VerificationSheetAnalytic {
|
||||
var params: [String : Any] {
|
||||
var params = additionalParams
|
||||
params["verification_session"] = verificationSessionId
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
||||
/// Logged when the sheet is presented
|
||||
struct VerificationSheetPresentedAnalytic: VerificationSheetAnalytic {
|
||||
let event = STPAnalyticEvent.verificationSheetPresented
|
||||
let verificationSessionId: String?
|
||||
let additionalParams: [String : Any] = [:]
|
||||
}
|
||||
|
||||
/// Logged when the sheet is closed by the end-user
|
||||
struct VerificationSheetClosedAnalytic: VerificationSheetAnalytic {
|
||||
let event = STPAnalyticEvent.verificationSheetClosed
|
||||
let verificationSessionId: String?
|
||||
let sessionResult: String
|
||||
|
||||
var additionalParams: [String : Any] {
|
||||
return [
|
||||
"session_result": sessionResult,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Logged if there's an error presenting the sheet
|
||||
struct VerificationSheetFailedAnalytic: VerificationSheetAnalytic {
|
||||
let event = STPAnalyticEvent.verificationSheetFailed
|
||||
let verificationSessionId: String?
|
||||
let error: Error
|
||||
|
||||
var additionalParams: [String : Any] {
|
||||
return [
|
||||
"error_dictionary": STPAnalyticsClient.serializeError(error as NSError)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to determine if we should log a failed analytic or closed analytic from the sheet's completion block
|
||||
struct VerificationSheetCompletionAnalytic {
|
||||
/// Returns either a `VerificationSheetClosedAnalytic` or `VerificationSheetFailedAnalytic` depending on the result
|
||||
static func make(
|
||||
verificationSessionId: String?,
|
||||
sessionResult result: IdentityVerificationSheet.VerificationFlowResult
|
||||
) -> VerificationSheetAnalytic {
|
||||
switch result {
|
||||
case .flowCompleted:
|
||||
assert(verificationSessionId != nil, "Verification Session ID is nil with completed result.")
|
||||
return VerificationSheetClosedAnalytic(
|
||||
verificationSessionId: verificationSessionId,
|
||||
sessionResult: "flow_completed"
|
||||
)
|
||||
case .flowCanceled:
|
||||
assert(verificationSessionId != nil, "Verification Session ID is nil with canceled result.")
|
||||
return VerificationSheetClosedAnalytic(
|
||||
verificationSessionId: verificationSessionId,
|
||||
sessionResult: "flow_canceled"
|
||||
)
|
||||
case .flowFailed(let error):
|
||||
return VerificationSheetFailedAnalytic(
|
||||
verificationSessionId: verificationSessionId,
|
||||
error: error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,4 +35,9 @@ import Foundation
|
|||
// MARK: - Card Scanning
|
||||
case cardScanSucceeded = "stripeios.cardscan_success"
|
||||
case cardScanCancelled = "stripeios.cardscan_cancel"
|
||||
|
||||
// MARK: - Identity Verification Flow
|
||||
case verificationSheetPresented = "stripeios.idprod.verification_sheet.presented"
|
||||
case verificationSheetClosed = "stripeios.idprod.verification_sheet.closed"
|
||||
case verificationSheetFailed = "stripeios.idprod.verification_sheet.failed"
|
||||
}
|
||||
|
|
|
@ -13,17 +13,20 @@ import XCTest
|
|||
final class IdentityVerificationSheetTest: XCTestCase {
|
||||
private let mockViewController = UIViewController()
|
||||
private let mockSecret = "vi_123_secret_456"
|
||||
private let mockAnalyticsClient = MockAnalyticsClient()
|
||||
|
||||
private var sheet: IdentityVerificationSheet!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
sheet = IdentityVerificationSheet(verificationSessionClientSecret: mockSecret)
|
||||
mockAnalyticsClient.reset()
|
||||
sheet = IdentityVerificationSheet(verificationSessionClientSecret: mockSecret, analyticsClient: mockAnalyticsClient)
|
||||
}
|
||||
|
||||
func testInvalidSecret() {
|
||||
var result: IdentityVerificationSheet.VerificationFlowResult?
|
||||
sheet = IdentityVerificationSheet(verificationSessionClientSecret: "bad secret")
|
||||
sheet = IdentityVerificationSheet(verificationSessionClientSecret: "bad secret", analyticsClient: mockAnalyticsClient)
|
||||
// TODO(mludowise|RUN_MOBILESDK-120): Using `presentInternal` instead of
|
||||
// `present` so we can run tests on our CI until it's updated to iOS 14.
|
||||
sheet.presentInternal(from: mockViewController) { (r) in
|
||||
|
@ -36,9 +39,45 @@ final class IdentityVerificationSheetTest: XCTestCase {
|
|||
case .invalidClientSecret = sheetError else {
|
||||
return XCTFail("Expected `IdentityVerificationSheetError.invalidClientSecret`")
|
||||
}
|
||||
|
||||
// Verify failed analytic is logged
|
||||
XCTAssertEqual(mockAnalyticsClient.loggedAnalytics.count, 1)
|
||||
guard let failedAnalytic = mockAnalyticsClient.loggedAnalytics.first as? VerificationSheetFailedAnalytic else {
|
||||
return XCTFail("Expected `VerificationSheetFailedAnalytic`")
|
||||
}
|
||||
XCTAssertNil(failedAnalytic.verificationSessionId)
|
||||
guard let analyticError = failedAnalytic.error as? IdentityVerificationSheetError,
|
||||
case .invalidClientSecret = analyticError else {
|
||||
return XCTFail("Expected `IdentityVerificationSheetError.invalidClientSecret`")
|
||||
}
|
||||
}
|
||||
|
||||
func testAnalytics() {
|
||||
// TODO(mludowise|IDPROD-1438)
|
||||
// TODO(mludowise|RUN_MOBILESDK-120): Using `presentInternal` instead of
|
||||
// `present` so we can run tests on our CI until it's updated to iOS 14.
|
||||
sheet.presentInternal(from: mockViewController) { _ in }
|
||||
|
||||
// Verify presented analytic is logged
|
||||
XCTAssertEqual(mockAnalyticsClient.loggedAnalytics.count, 1)
|
||||
guard let presentedAnalytic = mockAnalyticsClient.loggedAnalytics.first as? VerificationSheetPresentedAnalytic else {
|
||||
return XCTFail("Expected `VerificationSheetPresentedAnalytic`")
|
||||
}
|
||||
XCTAssertEqual(presentedAnalytic.verificationSessionId, "vi_123")
|
||||
|
||||
// Mock that flow is completed
|
||||
let mockVC = VerificationFlowWebViewController(clientSecret: VerificationClientSecret(string: mockSecret)!, delegate: nil)
|
||||
sheet.verificationFlowWebViewController(mockVC, didFinish: .flowCanceled)
|
||||
|
||||
// Verify closed analytic is logged
|
||||
XCTAssertEqual(mockAnalyticsClient.loggedAnalytics.count, 2)
|
||||
guard let closedAnalytic = mockAnalyticsClient.loggedAnalytics.last as? VerificationSheetClosedAnalytic else {
|
||||
return XCTFail("Expected `VerificationSheetClosedAnalytic`")
|
||||
}
|
||||
XCTAssertEqual(closedAnalytic.verificationSessionId, "vi_123")
|
||||
XCTAssertEqual(closedAnalytic.sessionResult, "flow_canceled")
|
||||
}
|
||||
|
||||
func testAnalyticsProductUsage() {
|
||||
XCTAssertEqual(mockAnalyticsClient.productUsage, ["IdentityVerificationSheet"])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// VerificationSheetAnalyticsTest.swift
|
||||
// StripeiOS Tests
|
||||
//
|
||||
// Created by Mel Ludowise on 3/12/21.
|
||||
// Copyright © 2021 Stripe, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable import Stripe
|
||||
|
||||
final class VerificationSheetAnalyticsTest: XCTestCase {
|
||||
|
||||
func testVerificationSheetFailedAnalyticEncoding() {
|
||||
let analytic = VerificationSheetFailedAnalytic(verificationSessionId: nil, error: IdentityVerificationSheetError.unknown(debugDescription: "some description"))
|
||||
XCTAssertEqual(analytic.params.count, 1)
|
||||
|
||||
guard let errorDict = analytic.params["error_dictionary"] as? [String: Any] else {
|
||||
return XCTFail("Expected `error_dictionary`")
|
||||
}
|
||||
XCTAssertEqual(errorDict["user_info"] as? [String: String], [
|
||||
NSDebugDescriptionErrorKey: "some description",
|
||||
NSLocalizedDescriptionKey: NSError.stp_unexpectedErrorMessage()
|
||||
])
|
||||
XCTAssertEqual(errorDict["code"] as? Int, 1)
|
||||
XCTAssertEqual(errorDict["domain"] as? String, "Stripe.IdentityVerificationSheetError")
|
||||
}
|
||||
|
||||
func testVerificationSheetCompletionAnalyticCompleted() {
|
||||
let analytic = VerificationSheetCompletionAnalytic.make(verificationSessionId: "session_id", sessionResult: .flowCompleted)
|
||||
guard let closedAnalytic = analytic as? VerificationSheetClosedAnalytic else {
|
||||
return XCTFail("Expected `VerificationSheetClosedAnalytic`")
|
||||
}
|
||||
|
||||
XCTAssertEqual(closedAnalytic.verificationSessionId, "session_id")
|
||||
XCTAssertEqual(closedAnalytic.sessionResult, "flow_completed")
|
||||
}
|
||||
|
||||
func testVerificationSheetCompletionAnalyticCanceled() {
|
||||
let analytic = VerificationSheetCompletionAnalytic.make(verificationSessionId: "session_id", sessionResult: .flowCanceled)
|
||||
guard let closedAnalytic = analytic as? VerificationSheetClosedAnalytic else {
|
||||
return XCTFail("Expected `VerificationSheetClosedAnalytic`")
|
||||
}
|
||||
|
||||
XCTAssertEqual(closedAnalytic.verificationSessionId, "session_id")
|
||||
XCTAssertEqual(closedAnalytic.sessionResult, "flow_canceled")
|
||||
}
|
||||
|
||||
func testVerificationSheetCompletionAnalyticFailed() {
|
||||
let analytic = VerificationSheetCompletionAnalytic.make(verificationSessionId: "session_id", sessionResult: .flowFailed(error: IdentityVerificationSheetError.unknown(debugDescription: "some description")))
|
||||
guard let failedAnalytic = analytic as? VerificationSheetFailedAnalytic else {
|
||||
return XCTFail("Expected `VerificationSheetFailedAnalytic`")
|
||||
}
|
||||
|
||||
XCTAssertEqual(failedAnalytic.verificationSessionId, "session_id")
|
||||
XCTAssert(failedAnalytic.error is IdentityVerificationSheetError)
|
||||
}
|
||||
}
|
|
@ -82,6 +82,7 @@ if skip_snapshot_tests
|
|||
"StripeiOS Tests/STPGenericInputPickerFieldSnapshotTests",
|
||||
"StripeiOS Tests/STPiDEALBankPickerInputFieldSnapshotTests",
|
||||
"StripeiOS Tests/STPiDEALFormViewSnapshotTests",
|
||||
"StripeiOS Tests/VerificationFlowWebViewSnapshotTests"
|
||||
"StripeiOS Tests/AfterpayPriceBreakdownViewSnapshotTests"
|
||||
]
|
||||
end
|
||||
|
@ -89,7 +90,7 @@ end
|
|||
destination_string = 'generic/platform=iOS Simulator'
|
||||
build_action = 'clean test'
|
||||
|
||||
if build_only
|
||||
if build_only
|
||||
# We'll want to clean outside this script.
|
||||
# If we clean here, we may unintentionally throw out the cache we built for other targets!
|
||||
build_action = 'build-for-testing'
|
||||
|
@ -103,7 +104,7 @@ else
|
|||
destination_string = 'platform=iOS Simulator'
|
||||
destination_string += ',name=' + device
|
||||
destination_string += ',OS=' + version
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
skip_tests_command = ""
|
||||
|
@ -148,4 +149,4 @@ Dir.chdir(__dir__ + '/..') do
|
|||
# If the build succeeded, create a placeholder cache key for the target.
|
||||
FileUtils.touch(__dir__ + '/../build-ci-tests/' + build_scheme + '.finished')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue