Skip to content

Commit 3724810

Browse files
Attestation on confirmation (#5762)
## Summary <!-- Simple summary of what was changed. --> Add an AttestationChallenge wrapper around StripeAttest that prewarms attestation on init and fetches an assertion with a timeout. Wrap PassiveCaptchaChallenge and AttestationChallenge in ConfirmationChallenge. Anywhere we previously passed PassiveCaptchaChallenge, we now pass in ConfirmationChallenge. ConfirmationChallenge creates the passive captcha and attestation challenges asynchronously in parallel and fetches their tokens with timeout in parallel and creates the radar options for the requests. Added playground switch so that we can test with/without attestation, similar to the passive captcha switch. Will be removed on release. Moved PassiveCaptchaChallenge into StripePaymentSheet, @_spi(STP) HCaptcha ## Motivation <!-- Why are you making this change? If it's for fixing a bug, if possible, please include a code snippet or example project that demonstrates the issue. --> Attestation support for HCaptcha project ## Testing <!-- How was the code tested? Be as specific as possible. --> <!-- Ignored Tests: Did you newly ignore a test in this PR? If so, please open an R4 incident so that the test can be re-enabled as soon as possible--> Added tests Example request: https://admin.corp.stripe.com/request-log/req_YXMjaq42sec9gb ## Changelog <!-- Is this a notable change that affects users? If so, add a line to `CHANGELOG.md` and prefix the line with one of the following: - [Added] for new features. - [Changed] for changes in existing functionality. - [Deprecated] for soon-to-be removed features. - [Removed] for now removed features. - [Fixed] for any bug fixes. - [Security] in case of vulnerabilities. --> N/A
1 parent b919bce commit 3724810

File tree

45 files changed

+792
-167
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+792
-167
lines changed

Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct CustomerSheetTestPlayground: View {
9595
SettingView(setting: $playgroundController.settings.preferredNetworksEnabled)
9696
SettingView(setting: $playgroundController.settings.cardBrandAcceptance)
9797
SettingView(setting: $playgroundController.settings.enablePassiveCaptcha)
98+
SettingView(setting: $playgroundController.settings.enableAttestationOnConfirmation)
9899
SettingView(setting: $playgroundController.settings.autoreload)
99100
TextField("headerTextForSelectionScreen", text: headerTextForSelectionScreenBinding)
100101
SettingView(setting: $playgroundController.settings.allowsRemovalOfLastSavedPaymentMethod)

Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ class CustomerSheetTestPlaygroundController: ObservableObject {
160160
}
161161
configuration.opensCardScannerAutomatically = settings.opensCardScannerAutomatically == .on
162162
configuration.enablePassiveCaptcha = settings.enablePassiveCaptcha == .on
163+
configuration.enableAttestationOnConfirmation = settings.enableAttestationOnConfirmation == .on
163164

164165
return configuration
165166
}

Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable {
162162
case off
163163
}
164164

165+
enum EnableAttestationOnConfirmation: String, PickerEnum {
166+
static var enumName: String { "Enable attestation on confirmation" }
167+
168+
case on
169+
case off
170+
}
171+
165172
enum OpensCardScannerAutomatically: String, PickerEnum {
166173
static let enumName: String = "opensCardScannerAutomatically"
167174
case on
@@ -176,6 +183,7 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable {
176183
var headerTextForSelectionScreen: String?
177184
var defaultBillingAddress: DefaultBillingAddress
178185
var enablePassiveCaptcha: EnablePassiveCaptcha
186+
var enableAttestationOnConfirmation: EnableAttestationOnConfirmation
179187
var autoreload: Autoreload
180188

181189
var attachDefaults: BillingDetailsAttachDefaults
@@ -202,6 +210,7 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable {
202210
headerTextForSelectionScreen: nil,
203211
defaultBillingAddress: .off,
204212
enablePassiveCaptcha: .on,
213+
enableAttestationOnConfirmation: .on,
205214
autoreload: .on,
206215
attachDefaults: .off,
207216
collectName: .automatic,

Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ struct PaymentSheetTestPlayground: View {
4242
.textInputAutocapitalization(.never)
4343
}
4444
SettingView(setting: $playgroundController.settings.enablePassiveCaptcha)
45+
SettingView(setting: $playgroundController.settings.enableAttestationOnConfirmation)
4546
Group {
4647
if playgroundController.settings.merchantCountryCode == .US {
4748
SettingView(setting: linkEnabledModeBinding)

Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,13 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
314314
case off
315315
}
316316

317+
enum EnableAttestationOnConfirmation: String, PickerEnum {
318+
static var enumName: String { "Enable attestation on confirmation" }
319+
320+
case on
321+
case off
322+
}
323+
317324
enum PaymentMethodSave: String, PickerEnum {
318325
static var enumName: String { "PaymentMethodSave" }
319326

@@ -682,6 +689,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
682689
var applePayButtonType: ApplePayButtonType
683690
var allowsDelayedPMs: AllowsDelayedPMs
684691
var enablePassiveCaptcha: EnablePassiveCaptcha
692+
var enableAttestationOnConfirmation: EnableAttestationOnConfirmation
685693
var paymentMethodSave: PaymentMethodSave
686694
var allowRedisplayOverride: AllowRedisplayOverride
687695
var paymentMethodRemove: PaymentMethodRemove
@@ -741,6 +749,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable {
741749
applePayButtonType: .buy,
742750
allowsDelayedPMs: .on,
743751
enablePassiveCaptcha: .on,
752+
enableAttestationOnConfirmation: .on,
744753
paymentMethodSave: .enabled,
745754
allowRedisplayOverride: .notSet,
746755
paymentMethodRemove: .enabled,

Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,10 @@ class PlaygroundController: ObservableObject {
214214
configuration.enablePassiveCaptcha = true
215215
}
216216

217+
if settings.enableAttestationOnConfirmation == .on {
218+
configuration.enableAttestationOnConfirmation = true
219+
}
220+
217221
if settings.shippingInfo != .off {
218222
configuration.allowsPaymentMethodsRequiringShippingAddress = true
219223
configuration.shippingDetails = { [weak self] in
@@ -334,6 +338,10 @@ class PlaygroundController: ObservableObject {
334338
configuration.enablePassiveCaptcha = true
335339
}
336340

341+
if settings.enableAttestationOnConfirmation == .on {
342+
configuration.enableAttestationOnConfirmation = true
343+
}
344+
337345
if settings.shippingInfo != .off {
338346
configuration.allowsPaymentMethodsRequiringShippingAddress = true
339347
configuration.shippingDetails = { [weak self] in

Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ class EmbeddedUITests: PaymentSheetUITestCase {
2020

2121
let cardButton = app.buttons["Card"]
2222
XCTAssertTrue(cardButton.waitForExistence(timeout: 10))
23-
// filter out async passive captcha logs
23+
// filter out async passive captcha and attestation logs
2424
let startupLog = analyticsLog.compactMap({ $0[string: "event"] })
25-
.filter({ !$0.starts(with: "luxe") }).filter({ !$0.starts(with: "elements.captcha.passive") })
25+
.filter({ !$0.starts(with: "luxe") }).filter({ !$0.starts(with: "elements.captcha.passive") && !($0.contains("attest")) })
2626
XCTAssertEqual(
2727
startupLog,
2828
// fraud detection telemetry should not be sent in tests, so it should report an API failure

Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ class PaymentSheetStandardUITests: PaymentSheetUITestCase {
242242

243243
app.buttons["Apple Pay, apple_pay"].waitForExistenceAndTap(timeout: 30) // Should default to Apple Pay
244244
XCTAssertEqual(
245-
// filter out async passive captcha logs
246-
analyticsLog.map({ $0[string: "event"] }).filter({ !($0?.starts(with: "elements.captcha.passive") ?? false) }),
245+
// filter out async passive captcha and attestation logs
246+
analyticsLog.map({ $0[string: "event"] }).filter({ !($0?.starts(with: "elements.captcha.passive") ?? false) && !($0?.contains("attest") ?? false) }),
247247
// fraud detection telemetry should not be sent in tests, so it should report an API failure
248248
["mc_load_started", "link.account_lookup.complete", "mc_load_succeeded", "fraud_detection_data_repository.api_failure", "mc_custom_init_customer_applepay", "mc_custom_sheet_savedpm_show"]
249249
)
@@ -628,12 +628,12 @@ class PaymentSheetDeferredUITests: PaymentSheetUITestCase {
628628

629629
XCTAssertEqual(
630630
// Ignore luxe_* analytics since there are a lot and I'm not sure if they're the same every time
631-
// filter out async passive captcha logs
632-
analyticsLog.map({ $0[string: "event"] }).filter({ $0 != "luxe_image_selector_icon_from_bundle" && $0 != "luxe_image_selector_icon_downloaded" && !($0?.starts(with: "elements.captcha.passive") ?? false) }),
631+
// filter out async passive captcha and attestation logs
632+
analyticsLog.map({ $0[string: "event"] }).filter({ $0 != "luxe_image_selector_icon_from_bundle" && $0 != "luxe_image_selector_icon_downloaded" && !($0?.starts(with: "elements.captcha.passive") ?? false) && !($0?.contains("attest") ?? false) }),
633633
// fraud detection telemetry should not be sent in tests, so it should report an API failure
634634
["mc_complete_init_applepay", "mc_load_started", "mc_load_succeeded", "fraud_detection_data_repository.api_failure", "mc_complete_sheet_newpm_show", "mc_lpms_render", "mc_form_shown", "link.inline_signup.shown"]
635635
)
636-
XCTAssertEqual(analyticsLog.filter({ !($0[string: "event"]?.starts(with: "elements.captcha.passive") ?? false || $0[string: "event"]?.starts(with: "link") ?? false) }).last?[string: "selected_lpm"], "card")
636+
XCTAssertEqual(analyticsLog.filter({ !($0[string: "event"]?.starts(with: "elements.captcha.passive") ?? false || $0[string: "event"]?.contains("attest") ?? false || $0[string: "event"]?.starts(with: "link") ?? false) }).last?[string: "selected_lpm"], "card")
637637

638638
try? fillCardData(app, container: nil)
639639

@@ -642,9 +642,26 @@ class PaymentSheetDeferredUITests: PaymentSheetUITestCase {
642642
let successText = app.staticTexts["Success!"]
643643
XCTAssertTrue(successText.waitForExistence(timeout: 10.0))
644644

645+
// filter out async attestation logs
645646
XCTAssertEqual(
646-
analyticsLog.suffix(10).map({ $0[string: "event"] }),
647-
["mc_form_interacted", "mc_card_number_completed", "mc_form_completed", "mc_confirm_button_tapped", "elements.captcha.passive.attach", "stripeios.confirmation_token_creation", "stripeios.paymenthandler.confirm.started", "stripeios.payment_intent_confirmation", "stripeios.paymenthandler.confirm.finished", "mc_complete_payment_newpm_success"]
647+
analyticsLog.map({ $0[string: "event"] }).filter({ !($0?.starts(with: "elements.captcha.passive") ?? false) && !($0?.contains("attest") ?? false) }).suffix(9),
648+
["mc_form_interacted", "mc_card_number_completed", "mc_form_completed", "mc_confirm_button_tapped", "stripeios.confirmation_token_creation", "stripeios.paymenthandler.confirm.started", "stripeios.payment_intent_confirmation", "stripeios.paymenthandler.confirm.finished", "mc_complete_payment_newpm_success"]
649+
)
650+
651+
XCTAssertEqual(
652+
analyticsLog.map({ $0[string: "event"] }).filter({ $0?.starts(with: "elements.captcha.passive") ?? false }),
653+
["elements.captcha.passive.init",
654+
"elements.captcha.passive.execute",
655+
"elements.captcha.passive.success",
656+
"elements.captcha.passive.attach", ]
657+
)
658+
659+
XCTAssertEqual(
660+
analyticsLog.map({ $0[string: "event"] }).filter({ $0?.starts(with: "elements.attestation.confirmation") ?? false }),
661+
["elements.attestation.confirmation.prepare",
662+
"elements.attestation.confirmation.prepare_failed",
663+
"elements.attestation.confirmation.request_token",
664+
"elements.attestation.confirmation.request_token_succeeded", ]
648665
)
649666

650667
// Make sure they all have the same session id

Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase {
116116
app.buttons["•••• 4242"].waitForExistenceAndTap()
117117
app.buttons["Continue"].tap() // For some reason, waitForExistenceAndTap() does not tap this!
118118
XCTAssertEqual(
119-
// filter out async passive captcha logs
120-
analyticsLog.map({ $0[string: "event"] }).filter({ !($0?.starts(with: "elements.captcha.passive") ?? false) }),
119+
// filter out async passive captcha and attestation logs
120+
analyticsLog.map({ $0[string: "event"] }).filter({ !($0?.starts(with: "elements.captcha.passive") ?? false) && !($0?.contains("attest") ?? false) }),
121121
// fraud detection telemetry should not be sent in tests, so it should report an API failure
122122
["mc_load_started", "link.account_lookup.complete", "mc_load_succeeded", "fraud_detection_data_repository.api_failure", "mc_custom_init_customer_applepay", "mc_custom_sheet_newpm_show", "mc_lpms_render", "mc_custom_paymentoption_savedpm_select", "mc_lpms_render", "mc_confirm_button_tapped"]
123123
)

StripeCore/StripeCore/Source/Analytics/STPAnalyticEvent.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,15 @@ import Foundation
316316
case passiveCaptchaError = "elements.captcha.passive.error"
317317
case passiveCaptchaAttach = "elements.captcha.passive.attach"
318318

319+
// MARK: - Attestation on Confirmation
320+
case attestationConfirmationPrepare = "elements.attestation.confirmation.prepare"
321+
case attestationConfirmationPrepareSucceeded = "elements.attestation.confirmation.prepare_succeeded"
322+
case attestationConfirmationPrepareFailed = "elements.attestation.confirmation.prepare_failed"
323+
case attestationConfirmationRequestToken = "elements.attestation.confirmation.request_token"
324+
case attestationConfirmationRequestTokenSucceeded = "elements.attestation.confirmation.request_token_succeeded"
325+
case attestationConfirmationRequestTokenFailed = "elements.attestation.confirmation.request_token_failed"
326+
case attestationConfirmationError = "elements.attestation.confirmation.error"
327+
319328
// MARK: - STPApplePayContext
320329
case applePayContextStarted = "stripeios.applepaycontext.confirm.started"
321330
case applePayContextFinished = "stripeios.applepaycontext.confirm.finished"

0 commit comments

Comments
 (0)